package com.pine.dbant.common.sql;

import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.tools.ant.taskdefs.SQLExec;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.sql.SQLException;
import java.util.*;

/**
 * @author xiaoyuan
 * @create 2022/11/9 15:48
 **/
@Setter
@Getter
public class MultiSqlParser {
    /**
     * SQL Statement delimiter
     */
    private String delimiter = ";";

    /**
     * The delimiter type indicating whether the delimiter will
     * only be recognized on a line by itself
     */
    private String delimiterType = SQLExec.DelimiterType.NORMAL;


    /**
     * Keep the format of a sql block?
     */
    private boolean keepformat = false;


    /**
     * should properties be expanded in text?
     * false for backwards compatibility
     *
     * @since Ant 1.7
     */
    private boolean expandProperties = true;

    /**
     * delimiters must match in case and whitespace is significant.
     *
     * @since Ant 1.8.0
     */
    private boolean strictDelimiterMatching = false;

    public  List<String> runStatements(String sqls)
            throws SQLException, IOException{
        return runStatements(new StringReader(sqls));
    }
    /**
     * read in lines and execute them
     *
     * @param reader the reader contains sql lines.
     * @param out    the place to output results.
     * @throws SQLException on sql problems
     * @throws IOException  on io problems
     */
    public  List<String> runStatements(Reader reader)
            throws SQLException, IOException {
        List<String> sqls = new ArrayList<>();

        StringBuffer sql = new StringBuffer();

        BufferedReader in = new BufferedReader(reader);

        String line;
        while ((line = in.readLine()) != null) {
            line = line.replaceAll("\\-\\-([^\\'\\r\\n]{0,}(\\'[^\\'\\r\\n]{0,}\\'){0,1}[^\\'\\r\\n]{0,}){0,}$","");
            if (!keepformat) {
                line = line.trim();
            }
//            if (expandProperties) {
//                line = getProject().replaceProperties(line);
//            }
            if (!keepformat) {

                if (line.startsWith("//")) {
                    continue;
                }
                if (line.startsWith("--")) {
                    continue;
                }
                StringTokenizer st = new StringTokenizer(line);
                if (st.hasMoreTokens()) {
                    String token = st.nextToken();
                    if ("REM".equalsIgnoreCase(token)) {
                        continue;
                    }
                }
            }

            sql.append(keepformat ? "\n" : " ").append(line);

            // SQL defines "--" as a comment to EOL
            // and in Oracle it may contain a hint
            // so we cannot just remove it, instead we must end it
            if (!keepformat && line.contains("--")) {
                sql.append("\n");
            }
            int lastDelimPos = lastDelimiterPosition(sql, line);
            if (lastDelimPos > -1) {
                addSql(sqls, sql.substring(0, lastDelimPos));
                //  execSQL(sql.substring(0, lastDelimPos), out);
                sql.replace(0, sql.length(), "");
            }
        }
        addSql(sqls, sql.toString());
        return sqls;
    }

    private void addSql(List<String> sqls, String sql) {
        // Catch any statements not followed by ;
        if (StringUtils.isNotBlank(sql)) {
            sqls.addAll(parserTableName(sql.replaceAll("(\\/\\/.*$)|(\\/\\*(.|\\s)*?\\*\\/)", "")));
        }
    }


    private List<String> parserTableName(String name) {
        List<Character> quoteChar = Arrays.asList( '`','"','\'');
        char splitChar = ';';
        List<Character> arr = new ArrayList<>();
        boolean quoteCharFlag = false;
        char c;
        char[] nameC = name.toCharArray();
        List<String> result = new ArrayList<>();
        for (int i = 0; i < nameC.length; i++) {
            c = nameC[i];
            if (quoteChar.contains(c)) {
                if (quoteCharFlag) {
                    quoteCharFlag = false;
                } else {
                    quoteCharFlag = true;
                }
                arr.add(c);
                continue;
            }
            if (c == splitChar && !quoteCharFlag) {
                result.add(toString(arr));
                arr.clear();
                continue;
            }
            arr.add(c);
        }
        if (!arr.isEmpty()) {
            result.add(toString(arr));
        }
        if (result.isEmpty()) {
            result.add(name);
        }
        return result;
    }
    private String toString(List<Character> arr ){
        StringBuffer stringBuffer  = new StringBuffer();
        for(Character c:arr){
            stringBuffer.append(c);
        }
        return stringBuffer.toString();
    }


    private  int lastDelimiterPosition(StringBuffer buf, String currentLine) {
        if (strictDelimiterMatching) {
            if ((delimiterType.equals(SQLExec.DelimiterType.NORMAL)
                    && StringUtils.endsWith(buf, delimiter))
                    || (delimiterType.equals(SQLExec.DelimiterType.ROW)
                    && currentLine.equals(delimiter))) {
                return buf.length() - delimiter.length();
            }
            // no match
            return -1;
        }
        String d = delimiter.trim().toLowerCase(Locale.ENGLISH);
        if (SQLExec.DelimiterType.NORMAL.equals(delimiterType)) {
            // still trying to avoid wasteful copying, see
            // StringUtils.endsWith
            int endIndex = delimiter.length() - 1;
            int bufferIndex = buf.length() - 1;
            while (bufferIndex >= 0 && Character.isWhitespace(buf.charAt(bufferIndex))) {
                --bufferIndex;
            }
            if (bufferIndex < endIndex) {
                return -1;
            }
            while (endIndex >= 0) {
                if (buf.substring(bufferIndex, bufferIndex + 1).toLowerCase(Locale.ENGLISH)
                        .charAt(0) != d.charAt(endIndex)) {
                    return -1;
                }
                bufferIndex--;
                endIndex--;
            }
            return bufferIndex + 1;
        }
        return currentLine.trim().toLowerCase(Locale.ENGLISH).equals(d)
                ? buf.length() - currentLine.length() : -1;
    }
}
